import { GameData, SavedGameMeta, GameSettings, AiCallMode, GameTurn, MacroFunction, MacroScope, ChatMessage } from '../types';
import {
  DEFAULT_MAX_HISTORY_LENGTH,
  DEFAULT_TEST_GAME_ID,
  DEFAULT_REQUEST_TIMEOUT,
  CALCULATION_HELPER_AI_TEMPLATE,
  NARRATIVE_AI_WITH_CALCS_TEMPLATE,
  HYBRID_STRUCTURED_DATA_TEMPLATE,
  TOOL_CALLING_TEMPLATE,
} from '../constants';
import { v4 as uuidv4 } from 'uuid';

// --- IndexedDB Setup ---
const DB_NAME = 'JRPGDatabase_v1';
const DB_VERSION = 1;
const STORES = {
  GAMES: 'games',
  SETTINGS: 'settings',
};
const SETTINGS_KEY = 'currentSettings';

let dbPromise: Promise<IDBDatabase> | null = null;

function getDb(): Promise<IDBDatabase> {
  if (dbPromise) {
    return dbPromise;
  }
  dbPromise = new Promise((resolve, reject) => {
    const request = indexedDB.open(DB_NAME, DB_VERSION);

    request.onerror = () => {
      console.error('IndexedDB error:', request.error);
      reject(new Error('Error opening IndexedDB.'));
    };

    request.onsuccess = () => {
      resolve(request.result);
    };

    request.onupgradeneeded = (event) => {
      const db = (event.target as IDBOpenDBRequest).result;
      if (!db.objectStoreNames.contains(STORES.GAMES)) {
        db.createObjectStore(STORES.GAMES, { keyPath: 'id' });
      }
      if (!db.objectStoreNames.contains(STORES.SETTINGS)) {
        db.createObjectStore(STORES.SETTINGS, { keyPath: 'id' });
      }
    };
  });
  return dbPromise;
}

// Generic transaction helper
async function performDbRequest<T>(storeName: string, mode: IDBTransactionMode, requestFn: (store: IDBObjectStore) => IDBRequest): Promise<T> {
    const db = await getDb();
    return new Promise<T>((resolve, reject) => {
        const transaction = db.transaction(storeName, mode);
        const store = transaction.objectStore(storeName);
        const request = requestFn(store);
        
        request.onsuccess = () => {
            resolve(request.result as T);
        };
        request.onerror = () => {
            console.error('DB Request Error:', request.error);
            reject(request.error);
        };

        transaction.onerror = () => {
            console.error('DB Transaction Error:', transaction.error);
            reject(transaction.error);
        };
    });
}
// --- End IndexedDB Setup ---


export const DEFAULT_MACRO_FUNCTIONS: MacroFunction[] = [
  {
    id: uuidv4(),
    name: 'random',
    description: '进行一次随机判定 (掷骰)。',
    parameters: [
      { name: 'min', description: '随机数下限', type: 'number' },
      { name: 'max', description: '随机数上限', type: 'number' },
      { name: 'success_threshold', description: '成功所需的点数 (小于等于)', type: 'number' },
    ],
    isNative: true,
    isEnabled: false,
    logic: 'random', // Key for the native function
    outputTemplate: '掷骰判定 (D${max} <= ${success_threshold}): 掷出 ${roll}，判定${success}。',
    scopes: [MacroScope.Planning, MacroScope.Narrative, MacroScope.Status, MacroScope.Tool]
  },
  {
    id: uuidv4(),
    name: 'clamp',
    description: '将一个数值限制在指定的最小和最大值之间。',
    parameters: [
        { name: 'value', description: '需要限制的数值', type: 'number' },
        { name: 'min', description: '允许的最小值', type: 'number' },
        { name: 'max', description: '允许的最大值', type: 'number' },
    ],
    isNative: true,
    isEnabled: false,
    logic: 'clamp',
    outputTemplate: '数值限制: ${value} 被限制在 [${min}, ${max}] 范围内，结果为 ${result}。',
    scopes: [MacroScope.Planning, MacroScope.Status, MacroScope.Tool]
  },
  {
    id: uuidv4(),
    name: 'roll_dice',
    description: '掷骰子，支持XdY+Z格式 (例如 "2d6+3", "d20-1")。',
    parameters: [
        { name: 'dice', description: '骰子表达式字符串', type: 'string' },
    ],
    isNative: true,
    isEnabled: false,
    logic: 'roll_dice',
    outputTemplate: '掷骰 (${dice}): 结果为 ${result} (明细: ${total}[掷骰] ${modifier >= 0 ? "+" : ""}${modifier}[调整])。',
    scopes: [MacroScope.Planning, MacroScope.Narrative, MacroScope.Status, MacroScope.Tool]
  }
];

export const NATIVE_FUNCTIONS_DOCUMENTATION = [
    {
        name: 'random',
        description: '进行一次随机判定，并判断是否成功。',
        parameters: [
            { name: 'min', description: '随机数下限 (包含)' },
            { name: 'max', description: '随机数上限 (包含)' },
            { name: 'success_threshold', description: '成功所需的点数 (小于等于此值则成功)' },
        ],
        returns: ['roll (掷骰结果)', 'success (布尔值, 是否成功)'],
        exampleTemplate: '掷骰判定 (D${max} <= ${success_threshold}): 掷出 ${roll}，判定为 ${success}。'
    },
    {
        name: 'clamp',
        description: '将一个数值限制在指定的最小和最大值之间。常用于确保生命值不低于0或不超过上限。',
        parameters: [
            { name: 'value', description: '需要被限制的数值' },
            { name: 'min', description: '允许的最小值' },
            { name: 'max', description: '允许的最大值' },
        ],
        returns: ['result (最终结果)'],
        exampleTemplate: '数值限制: 将 ${value} 限制在 [${min}, ${max}] 之间，结果为 ${result}。'
    },
    {
        name: 'roll_dice',
        description: '根据标准掷骰表达式（如 "2d6+3"）计算结果。',
        parameters: [
            { name: 'dice', description: '骰子表达式字符串 (格式为 XdY+Z, X和Z可选)' },
        ],
        returns: ['result (总结果)', 'total (骰子点数和)', 'modifier (调整值)'],
        exampleTemplate: '掷骰 (${dice}): 结果为 ${result} (明细: ${total}[掷骰] + ${modifier}[调整])。'
    }
];

export const MACRO_TUTORIAL_CONTENT = `
### 宏函数使用指南

宏函数是定义可复用游戏逻辑的强大工具。现在，它们可以在不同AI的不同阶段被调用，提供了前所未有的灵活性。

---

### 核心概念：AI调用流程 (混合模式)

为了更好地理解宏函数，首先需要了解混合模式下的AI工作流程。当玩家做出选择后，系统会启动一个由多个专业AI协作的链条：

1.  **规划AI (可选)**: 如果启用了“计算助手AI”，这个AI会首先启动。
    *   **任务**: 分析玩家行动，制定一个包含“叙事指引”和“函数调用”的计划 (JSON格式)。
    *   **宏调用**: 调用**规划 (planning)**作用域的宏函数。例如，计算伤害、进行成功检定等。
2.  **本地计算**: 系统会立即执行规划AI要求的所有函数调用，得到精确的计算结果（如“造成了85点伤害”）。
3.  **叙事AI**: 
    *   **任务**: 将规划AI的指引和本地计算结果，转化为生动、流畅的游戏叙事（纯文本流式输出）。
    *   **宏调用**: 叙事AI可以在其生成的文本中，直接嵌入**叙事 (narrative)**作用域的函数，例如 \`roll_dice("d100")\`。系统会在流式输出结束后，自动将这些文本替换为函数的执行结果。**叙事宏不应修改游戏状态**。
4.  **数据AI**: 
    *   **任务**: 阅读刚刚由叙事AI生成的完整故事，并据此生成结构化的游戏数据 (JSON格式)，包括玩家选项、图片提示、关键信息更新以及状态变更。
    *   **宏调用**: 除了直接生成状态变更（JSON Patch），数据AI还可以调用**状态 (status)**作用域的宏函数来执行更复杂的逻辑，例如使用物品。

---

### 新功能: 宏函数作用域 (Scopes)

每个宏函数现在都有一个“作用域”设置，决定了它可以被哪个AI使用。这使得函数管理更加清晰和高效。

*   **规划 (Planning)**: 仅供“规划AI”使用。通常是用于计算核心游戏机制（伤害、检定、资源消耗等）的函数。
*   **叙事 (Narrative)**: 仅供“叙事AI”使用。通常是用于生成动态文本的函数，如掷骰子、随机描述等。这些函数**不应该修改游戏状态**（即不应返回 \`patch\`）。
*   **状态 (Status)**: 仅供“数据AI”使用。用于执行复杂的状态更新，这些更新难以用简单的数值加减（JSON Patch）来描述，例如使用一个会产生多种效果的药水。
*   **工具 (Tool)**: **[新]** 供实验性的“函数调用模式”使用。在此模式下，单个AI会同时负责叙事和调用函数。此作用域下的函数应该设计为能被AI直接理解和调用的工具。

你可以在宏函数编辑器中为一个函数选择一个或多个作用域。

#### 叙事函数调用示例

当叙事AI在生成故事时，它可以像这样直接写出函数：
\`\`\`
...你感觉一股未知的力量在涌动，于是你进行了一次意志检定: roll_dice("d20+5")。与此同时，一个随机事件发生了...
\`\`\`
在AI输出完毕后，系统会自动执行 \`roll_dice("d20+5")\`，然后将上面的文本替换为：
\`\`\`
...你感觉一股未知的力量在涌动，于是你进行了一次意志检定: 掷骰 (d20+5): 结果为 22 (明细: 17[掷骰] +5[调整])。与此同时，一个随机事件发生了...
\`\`\`

#### 状态函数调用示例

当数据AI分析完叙事后，如果它认为角色使用了治疗药水，它可以返回如下JSON：
\`\`\`json
{
  "options": ["继续探索", "休息一下"],
  "statusPanelPatch": [], // 简单的变更可以放这里
  "keyInfoUpdate": "玩家使用了治疗药水。",
  "function_calls": [
    {
      "name": "use_potion",
      "args": { "character_name": "主角" }
    }
  ]
}
\`\`\`
系统会执行 \`use_potion\` 函数，该函数内部逻辑会计算治疗量并生成修改角色生命值和药水数量的JSON Patch，最终应用到游戏状态上。

---

### 宏函数编写指南 (高级)

- **环境**: 你的代码在一个安全、隔离的环境中运行。你无法访问网页内容 (\`window\`, \`document\`) 或进行网络请求 (\`fetch\`)。
- **参数**:
    1. \`args\`: 一个包含了所有你在UI中定义的参数的对象。例如，参数 \`power\` 可以通过 \`args.power\` 访问。
    2. \`state\`: 当前状态面板的**只读副本**。你可以用它来读取游戏状态，但不能直接修改。
- **返回值**: 你的代码 **必须** 使用 \`return\` 语句返回一个值或一个对象。
    - **简单返回**: 如果只返回一个值（如数字或字符串），它将被用作输出模板中的 \`\${result}\`。
    - **对象返回**: 你可以返回一个包含多个值的对象，然后在模板中使用点表示法访问它们，例如 \`\${result.damage}\`。
- **修改游戏状态 (重要)**: 你不能直接修改 \`state\` 对象。要改变游戏状态，你必须在返回的对象中包含一个名为 \`patch\` 的键。这个键的值必须是一个 [JSON Patch](https://jsonpatch.com/) 操作数组。**只有作用域包含“规划”或“状态”的宏函数才能有效应用状态变更。**

**示例：使用治疗药水 (状态/规划作用域)**
\`\`\`javascript
// 'state' 是当前状态面板的只读副本。
const char = state.character;
const inventory = state.inventory;

if (inventory.potions <= 0) {
  return { 
    result: "发现背包里没有治疗药水了！", 
    patch: [] // 不进行任何状态变更
  };
}

const heal_amount = 50;
const new_hp = Math.min(char.max_hp, char.hp + heal_amount);
const recovered_hp = new_hp - char.hp;

// 返回一个对象，包含'result'用于输出模板，以及'patch'来修改状态
return {
  result: \`使用了1瓶药水，恢复了 \${recovered_hp} 点生命。\`,
  patch: [
    { "op": "replace", "path": "/character/hp", "value": new_hp },
    { "op": "replace", "path": "/inventory/potions", "value": inventory.potions - 1 }
  ]
};
\`\`\`
*对应输出模板: \`药水效果: \${result}\`*

- **安全**: 为了防止游戏卡死，每个宏函数的执行时间被限制在 **500毫秒** 以内。如果代码运行超时 (例如，因为一个无限循环)，它将被强制终止并返回一个错误。

---

### [新] 工具 (Tool) 作用域宏函数编写指南 (函数调用模式)

当您为实验性的“函数调用模式”编写宏时，其设计理念是实现**“即时状态同步”**，这与传统模式有根本性的不同。

#### 简要流程介绍
1.  **AI调用**: AI在叙事时调用您的工具函数。
2.  **函数执行**: 您的函数代码被执行，并返回一个包含两部分的对象。
3.  **即时更新**: 系统**立即**使用您函数返回的\`patch\`部分来更新游戏状态（状态栏UI立刻变化）。
4.  **返回结果**: 系统将您函数返回的\`result\`部分发送给AI。
5.  **继续叙事**: AI将收到的结果融入到后续的叙事中。

#### 核心理念：由AI提供路径

在此模式下，我们依赖AI的强大理解能力来定位需要修改的数据。

1.  **AI提供路径**: 您需要通过叙事提示词，引导AI在调用函数时，提供一个**精确的 [JSON Pointer](https://datatracker.ietf.org/doc/html/rfc6902) 路径**作为参数，指向它希望修改的目标。例如，AI的函数调用参数可能包含 \`target_path: "/enemies/0"\`。
2.  **宏函数负责执行**: 您的代码接收这个路径，用它来读取当前状态、执行计算，并**使用同一个路径**生成状态变更补丁(\`patch\`)。

#### 关键：返回值结构

“工具”作用域的宏函数**必须**返回一个包含以下两个键的对象：

-   \`result\`: (字符串) 这是返回给AI看的结果描述。AI会把这个字符串融入到它的叙事中。
-   \`patch\`: (JSON Patch数组) 这是返回给游戏系统用的状态变更指令。系统在收到后会立刻应用它，实现状态栏的即时更新。

如果您的函数不返回这个结构，新模式将无法正常工作。

#### 完整示例：重构 damage 函数 (工具模式版)

这是一个为“工具”作用域量身定制的 \`damage\` 函数，它体现了“由AI提供路径”的原则：

\`\`\`javascript
// 'args' 包含了AI调用时提供的参数, e.g., { attacker_name: "刻晴", target_path: "/enemies/0", ... }
// 'state' 是当前状态的只读副本，用于读取数据。

// 步骤 1: 根据AI提供的路径读取目标数据
// (为简化示例，我们假设路径总是有效，实际应用中可能需要错误处理)
const pathParts = args.target_path.split('/').slice(1);
let target = state;
try {
    for (const part of pathParts) {
        target = target[part];
    }
} catch (e) {
    return { 
        result: \`计算错误: 未能在状态中找到路径 "\${args.target_path}"。\`,
        patch: [] 
    };
}

// 步骤 2: 执行计算
const baseDamage = args.attack_power * args.skill_multiplier;
// ... (其他伤害计算逻辑)
const finalDamage = Math.round(Math.max(1, baseDamage * (1 - target.defense_mod)));
const newHp = target.hp - finalDamage;

// 步骤 3: 使用AI提供的路径生成结果和状态变更补丁
return {
    // 这是给AI看的，它会把这句话写进故事里
    result: \`\${args.attacker_name} 对 \${target.name} 造成了 \${finalDamage} 点伤害。\`,
    
    // 这是给系统用的，会立刻更新状态栏
    patch: [
        { "op": "replace", "path": \`\${args.target_path}/hp\`, "value": newHp }
    ]
};
\`\`\`

#### 总结
为“函数调用模式”编写宏，请牢记以下三点：
1.  **输入是路径** (由AI提供)。
2.  **代码要使用路径** 来读取数据和生成补丁。
3.  **输出必须是** \`{ result: "...", patch: [...] }\` 的格式。
`;


// Migrated Game Save Functions

export async function getSavedGamesMeta(): Promise<SavedGameMeta[]> {
    try {
        const games = await performDbRequest<GameData[]>(STORES.GAMES, 'readonly', store => store.getAll());
        if (!games) return [];
        
        return games.map(game => ({
            id: game.id,
            name: game.rulebook.gameName,
            lastPlayedTimestamp: game.lastPlayedTimestamp || 0,
            isDefaultTestGame: game.isDefaultTestGame,
            originalGameId: game.originalGameId,
        })).sort((a,b) => (b.isDefaultTestGame ? -1 : 0) - (a.isDefaultTestGame ? -1 : 0) || b.lastPlayedTimestamp - a.lastPlayedTimestamp);
    } catch (e) {
        console.error("Failed to get saved games metadata from IndexedDB:", e);
        return [];
    }
}

export function recalculateAggregatedKeyInfo(history: GameTurn[]): string {
  return history
    .map((turn, index) => `回合 ${index + 1} 关键信息：\n${turn.turnKeyInfo || "无"}`)
    .join('\n\n')
    .trim();
}

export async function saveGame(gameData: GameData): Promise<void> {
  const updatedGameData = {
    ...gameData,
    aggregatedKeyInfo: recalculateAggregatedKeyInfo(gameData.gameHistory),
    lastPlayedTimestamp: Date.now(),
  };
  await performDbRequest(STORES.GAMES, 'readwrite', store => store.put(updatedGameData));
}

export async function loadGame(gameId: string): Promise<GameData | null> {
    const game = await performDbRequest<GameData | undefined>(STORES.GAMES, 'readonly', store => store.get(gameId));
    if (game) {
        if (!game.aggregatedKeyInfo && game.gameHistory.length > 0) {
          game.aggregatedKeyInfo = recalculateAggregatedKeyInfo(game.gameHistory);
        }
        if (game.lastPlayedTimestamp === undefined) {
            game.lastPlayedTimestamp = 0;
        }
        if (!game.macroFunctions) { // Backwards compatibility for very old saves
            game.macroFunctions = JSON.parse(JSON.stringify(DEFAULT_MACRO_FUNCTIONS));
        } else {
            // Ensure all macros have the `isEnabled` and `scopes` property for backward compatibility.
            game.macroFunctions.forEach(macro => {
                if (macro.isEnabled === undefined) {
                    macro.isEnabled = true; // Enable macros in old saves by default to not break them.
                }
                if (!macro.scopes) {
                    macro.scopes = [MacroScope.Planning]; // Assign a safe default
                }
            });

            // Find native macros that exist in the save and update them with the latest default properties.
            // This ensures compatibility (e.g., adds new scopes) without re-adding functions the user has deleted.
            game.macroFunctions.forEach(savedMacro => {
                if (savedMacro.isNative) {
                    const defaultMatch = DEFAULT_MACRO_FUNCTIONS.find(dm => dm.name === savedMacro.name);
                    if (defaultMatch) {
                        // Update properties from the default template but keep the saved ID and enabled state.
                        savedMacro.description = defaultMatch.description;
                        savedMacro.parameters = defaultMatch.parameters;
                        savedMacro.logic = defaultMatch.logic;
                        savedMacro.outputTemplate = defaultMatch.outputTemplate;
                        // Merge scopes to add any new ones from an update.
                        savedMacro.scopes = [...new Set([...savedMacro.scopes, ...defaultMatch.scopes])];
                    }
                }
            });
        }
        
        // Backward compatibility for system prompts
        if (!game.aiPrompts.calculationHelperSystemPrompt) {
          game.aiPrompts.calculationHelperSystemPrompt = CALCULATION_HELPER_AI_TEMPLATE;
        }
        if (!game.aiPrompts.narrativeWithCalcsSystemPrompt) {
          game.aiPrompts.narrativeWithCalcsSystemPrompt = NARRATIVE_AI_WITH_CALCS_TEMPLATE;
        }
        if (!game.aiPrompts.hybridStructuredDataSystemPrompt) {
          game.aiPrompts.hybridStructuredDataSystemPrompt = HYBRID_STRUCTURED_DATA_TEMPLATE;
        }
        if (!game.aiPrompts.toolCallingSystemPrompt) {
          game.aiPrompts.toolCallingSystemPrompt = TOOL_CALLING_TEMPLATE;
        }
        if (!game.aiPromptsChatHistory) {
          game.aiPromptsChatHistory = {};
        }
        if (!game.rulebookChatHistory) {
          game.rulebookChatHistory = [];
        }

        return game;
    }
    return null;
}

export async function deleteGame(gameId: string): Promise<void> {
  await performDbRequest(STORES.GAMES, 'readwrite', store => store.delete(gameId));
}

export async function duplicateGame(originalGameId: string): Promise<GameData | null> {
  const originalGame = await loadGame(originalGameId);
  if (!originalGame) return null;

  const allMetas = await getSavedGamesMeta();
  let newName = `${originalGame.rulebook.gameName} (副本)`;
  let counter = 2;
  const existingNames = allMetas.map(g => g.name);
  while (existingNames.includes(newName)) {
    newName = `${originalGame.rulebook.gameName} (副本 ${counter})`;
    counter++;
  }

  const duplicatedGame: GameData = {
    ...JSON.parse(JSON.stringify(originalGame)), // Deep copy
    id: uuidv4(),
    rulebook: {
      ...originalGame.rulebook,
      gameName: newName,
    },
    lastPlayedTimestamp: 0,
    originalGameId: originalGame.id,
    isDefaultTestGame: false,
  };
  
  await saveGame(duplicatedGame);
  return duplicatedGame;
}

export async function ensureDefaultTestGameExists(): Promise<void> {
  const existingTestGame = await loadGame(DEFAULT_TEST_GAME_ID);
  if (!existingTestGame) {
    const testGameData: GameData = {
      id: DEFAULT_TEST_GAME_ID,
      rulebook: {
        gameName: "API 功能测试 (默认)",
        backgroundStory: "这是一个用于测试API连接和基本功能的特殊游戏存档。它没有预设的AI提示词，您需要在创建新游戏或在此处手动配置它们以进行完整测试。\n\n开始游戏后，AI会尝试生成初始剧情。如果配置了图像模型，也会尝试生成图片。",
        rules: "1. 在“设置”中配置您的API渠道和模型。\n2. 确保至少有一个默认文本模型和可选的默认叙事图片模型。\n3. 开始或载入此游戏。\n4. 游戏将自动尝试使用AI生成初始场景。\n5. 您可以从游戏内“编辑AI提示词”来修改或添加提示词。\n6. 您也可以通过“编辑游戏规则/故事”来修改本存档的基础设定。",
      },
      aiPrompts: {
        scenarioGenerationPrompt: "",
        statusPanelPrompt: "",
        keyInfoExtractionPrompt: "",
        calculationHelperSystemPrompt: CALCULATION_HELPER_AI_TEMPLATE,
        narrativeWithCalcsSystemPrompt: NARRATIVE_AI_WITH_CALCS_TEMPLATE,
        hybridStructuredDataSystemPrompt: HYBRID_STRUCTURED_DATA_TEMPLATE,
        toolCallingSystemPrompt: TOOL_CALLING_TEMPLATE,
      },
      aiPromptsChatHistory: {},
      rulebookChatHistory: [],
      macroFunctions: JSON.parse(JSON.stringify(DEFAULT_MACRO_FUNCTIONS)), // Deep copy
      gameHistory: [],
      aggregatedKeyInfo: '',
      lastPlayedTimestamp: 0,
      isDefaultTestGame: true,
    };
    await saveGame(testGameData);
  }
}

export function exportGame(gameData: GameData): void {
  const filename = `${gameData.rulebook.gameName.replace(/\s+/g, '_')}_save.json`;
  const jsonStr = JSON.stringify(gameData, null, 2);
  const blob = new Blob([jsonStr], { type: "application/json" });
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url;
  a.download = filename;
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  URL.revokeObjectURL(url);
}

export function importGame(file: File): Promise<GameData> {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = (event) => {
      try {
        const jsonStr = event.target?.result as string;
        const gameData = JSON.parse(jsonStr) as GameData;
        if (gameData.id && gameData.rulebook && gameData.aiPrompts && Array.isArray(gameData.gameHistory)) {
          if (!gameData.aggregatedKeyInfo && gameData.gameHistory.length > 0) {
            gameData.aggregatedKeyInfo = recalculateAggregatedKeyInfo(gameData.gameHistory);
          }
          if (gameData.lastPlayedTimestamp === undefined) {
            gameData.lastPlayedTimestamp = Date.now();
          }
          if (!gameData.macroFunctions) { // Backwards compatibility
             gameData.macroFunctions = JSON.parse(JSON.stringify(DEFAULT_MACRO_FUNCTIONS));
          }
          // Backwards compatibility for macros without scopes
          gameData.macroFunctions.forEach(mf => {
            if (mf.isEnabled === undefined) {
                mf.isEnabled = true;
            }
            if (!mf.scopes) {
                mf.scopes = [MacroScope.Planning]; // Default to planning scope
            }
          });
          if (!gameData.aiPromptsChatHistory) { // Backwards compatibility for chat history
            gameData.aiPromptsChatHistory = {};
          }
          if (!gameData.rulebookChatHistory) {
              gameData.rulebookChatHistory = [];
          }
          resolve(gameData);
        } else {
          reject(new Error("无效的游戏存档文件格式。"));
        }
      } catch (e) {
        reject(new Error("读取或解析游戏存档文件失败。"));
      }
    };
    reader.onerror = () => reject(new Error("读取文件失败。"));
    reader.readAsText(file);
  });
}

// Settings management
export async function loadSettings(): Promise<GameSettings> {
  const loadedSettings = await performDbRequest<GameSettings & { id?: string; defaultImageModelId?: string | null } | undefined>(STORES.SETTINGS, 'readonly', store => store.get(SETTINGS_KEY));
  
  const defaultSettings: GameSettings = {
    apiChannels: [],
    modelConfigs: [],
    defaultTextModelId: null,
    defaultNarrativeImageModelId: null,
    maxHistoryLengthToDisplay: DEFAULT_MAX_HISTORY_LENGTH,
    aiCallMode: AiCallMode.Hybrid,
    alwaysShowManualParseModal: false,
    corsProxyUrl: '',
    requestTimeout: DEFAULT_REQUEST_TIMEOUT,
    hybridModeStreamNarrative: true,
    enableCalculationHelperAi: false,
    enableFunctionCallingMode: false,
    showCalculationHelperAiPlan: false,
    showFunctionCallDebug: false,
    defaultHybridPlanningModelId: null,
    defaultHybridNarrativeModelId: null,
    defaultHybridStatusModelId: null,
  };

  if (!loadedSettings) {
    return defaultSettings;
  }
  
  // Migration from old defaultImageModelId
  if (!loadedSettings.defaultNarrativeImageModelId && loadedSettings.defaultImageModelId) {
    loadedSettings.defaultNarrativeImageModelId = loadedSettings.defaultImageModelId;
  }
  delete loadedSettings.defaultImageModelId;
  delete loadedSettings.id;

  return { ...defaultSettings, ...loadedSettings };
}

export async function saveSettings(settings: GameSettings): Promise<void> {
  const settingsToSave = { ...settings, id: SETTINGS_KEY };
  delete (settingsToSave as any).defaultImageModelId;
  await performDbRequest(STORES.SETTINGS, 'readwrite', store => store.put(settingsToSave));
}